package p05;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * BeanFactory后处理器ConfigurationClassPostProcessor是如何解析@ComponentScan注解的呢？
 * 这节模拟解析过程
 * 假设这里已经得到了Config类
 * CachingMetadataReaderFactory类读取类元信息：效率比反射高
 *
 * @author Chen weishuai
 * @since 2025/7/23
 */
public class ComponentScanDemo {

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        /**
         * 往容器中添加我们自定义的beanFactory后处理器
         * 容器refresh时，会得到所有beanFactory后处理器类型的bean，
         * 然后一一运行他们(就是第二章的:postProcessor.postProcessBeanFactory(beanFactory))
         * 运行的本质就是调用后处理器的 postProcessBeanFactory 方法 (beanFactory后处理器都实现了BeanFactoryPostProcessor,
         * 所以肯定有postProcessBeanFactory方法)
         */
        context.registerBean(MyComponentScan.class);
        // 刷新容器
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }

    }

    /**
     * 这是一个BeanFactoryPostProcessor
     */
    static class MyComponentScan implements BeanFactoryPostProcessor {
        // beanFactory后处理器，加入容器后，执行context.refresh()就会回调这个方法
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            // 下面两行代码将ConfigurableListableBeanFactory类型的BeanFactory变成GenericApplicationContext类型
            DefaultListableBeanFactory b = (DefaultListableBeanFactory) beanFactory;
            GenericApplicationContext context = new GenericApplicationContext(b);
            // 读取类的元信息对象
            CachingMetadataReaderFactory cachingMetadataReaderFactory = new CachingMetadataReaderFactory();
            // 为bean命名
            AnnotationBeanNameGenerator annotationBeanNameGenerator = new AnnotationBeanNameGenerator();
            /**
             * 解析@ComponentScan思路：
             *  1.判断某个类上是否有@ComponentScan注解
             *  2.如果有，得到该注解的basePackages属性，将其转换为一个正确的路径字符串
             *  3.根据路径字符串，获得对应路径下所有资源
             *  4.通过 CachingMetadataReaderFactory 获取每一个资源的类元信息和注解信息，比如可以获取该类有没有添加注解
             *  5.加了注解，则将其加入容器
             * 这里假设已经提前得到了 Config 类，所以直接使用
             */
            ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
            if (componentScan != null) {
                // 要扫描的包路径可能有多个
                for (String path : componentScan.basePackages()) {
                    path = "classpath*:" + path.replace(".", "/") + "/**/*.class";
                    // 根据上面得到的通配符找到资源(class文件)，下面两种根据通配符获取资源的代码等价
                    try {
                        Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
                        for (Resource resource : resources) {
                            // 读取class元信息
                            MetadataReader metadataReader = cachingMetadataReaderFactory.getMetadataReader(resource);
                            // 获取该类有没有加指定注解及其派生注解(hasAnnotation:判断是否有指定注解;hasMetaAnnotation:判断是否有指定注解及其派生注解)
                            AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
                            if (metadata.hasAnnotation(Component.class.getName()) || metadata.hasMetaAnnotation(Component.class.getName())) {
                                System.out.println("扫描到了类:" + resource + ", 准备加入容器");
                                // 生成该bean的bean定义对象
                                AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(metadataReader.getClassMetadata().getClassName()).getBeanDefinition();
                                // 根据bean定义生成该bean的名字
                                String beanName = annotationBeanNameGenerator.generateBeanName(beanDefinition, context.getDefaultListableBeanFactory());
                                // 将bean定义注册进容器
                                context.registerBeanDefinition(beanName, beanDefinition);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
